package com.luo.sc.oidc.authserver.config;

import com.luo.sc.oidc.authserver.controller.login.LoginPageController;
import com.luo.sc.oidc.authserver.handler.login.UniLoginAuthenticationSecurityConfig;
import com.luo.sc.oidc.authserver.handler.login.UniLoginUserDetailsPasswordMatcherService;
import com.luo.sc.oidc.authserver.handler.login.UniLoginUserDetailsService;
import com.luo.sc.oidc.authserver.handler.oidc.OidcEndSessionSingleLogoutSuccessHandler;
import com.luo.sc.oidc.authserver.service.OidcAuthorizationService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * SpringSecurity 表单登录配置
 *
 * @author luo
 * @date 2022-02-17
 */
@EnableConfigurationProperties(Oauth2ServerProps.class)
public class FormSecurityConfig implements WebMvcConfigurer {

    /**
     * OAuth2认证服务器端配置属性
     */
    private Oauth2ServerProps oauth2ServerProps;
    /**
     * Client注册信息DAO
     */
    private RegisteredClientRepository registeredClientRepository;
    /**
     * 认证信息DAO
     */
    private OidcAuthorizationService oidcAuthorizationService;

    public FormSecurityConfig(Oauth2ServerProps oauth2ServerProps, RegisteredClientRepository registeredClientRepository, OidcAuthorizationService oidcAuthorizationService) {
        this.oauth2ServerProps = oauth2ServerProps;
        this.registeredClientRepository = registeredClientRepository;
        this.oidcAuthorizationService = oidcAuthorizationService;
    }

    /**
     * 配置form认证（自定义登录、登出等）
     */
    @Bean
    public SecurityFilterChain formSecurityFilterChain(HttpSecurity http, UniLoginUserDetailsService uniLoginUserDetailsService, RequestCache requestCache) throws Exception {
        http
                //认证配置
                .authorizeHttpRequests(authorize -> authorize
                        .mvcMatchers(this.oauth2ServerProps.getLoginPageUrl(), this.oauth2ServerProps.getLogoutRedirectDefaultUrl()).permitAll()
                        .mvcMatchers(this.oauth2ServerProps.getCaptchaUrl()).permitAll()
                        .mvcMatchers(this.oauth2ServerProps.getStaticResourceWhiteList()).permitAll()
                        .anyRequest().authenticated()
                )
                //支持跨域(放过OPTIONS请求，集成端需自定义CorsFilter Bean设置CORS相关配置)
                .cors().and()
                //忽略白名单接口的CSRF验证
                .csrf(csrf -> csrf
                        .ignoringAntMatchers(this.oauth2ServerProps.getStaticResourceWhiteList()))
                //form表单登录配置
                .formLogin(form -> form
                        //登录页URL
                        .loginPage(this.oauth2ServerProps.getLoginPageUrl())
                        .loginProcessingUrl(this.oauth2ServerProps.getLoginProcessingUrl())
                )
                //登出配置
                .logout(logout -> logout
                        //登出页面URL
                        .logoutUrl(this.oauth2ServerProps.getLogoutPageUrl())
                        //登出成功处理器 - 支持OIDC SLO
                        .logoutSuccessHandler(new OidcEndSessionSingleLogoutSuccessHandler(registeredClientRepository, oidcAuthorizationService, this.oauth2ServerProps))
                )
                //.sessionManagement(session -> session
                //        .sessionFixation(fixation -> fixation
                //                //.changeSessionId()
                //                .none()
                //        )
                //        //.sessionAuthenticationStrategy()
                //)
                //.rememberMe(rememberMe -> rememberMe
                //        .alwaysRemember(false)
                //        //登录表单对应的记住我参数名（默认remember-me，对应值true|yes|on|1）
                //        .rememberMeParameter("remember-me")
                //        //记住我的浏览器端cookie名（默认remember-me）
                //        .rememberMeCookieName("remember-me")
                //        //记住我token失效时间（默认2周）
                //        .tokenValiditySeconds(1209600)
                //
                //        //.rememberMeServices(new TokenBasedRememberMeServices(key, userDetailServices))
                //        //设置使用DB进行持久化记住我Token（若不设置则默认使用TokenBased（即在浏览器端记录hash cookie））
                //        //.tokenRepository(new JdbcTokenRepositoryImpl())
                //
                //)
                //通用登录模型配置
                .apply(new UniLoginAuthenticationSecurityConfig(this.oauth2ServerProps, uniLoginUserDetailsService, requestCache));

        return http.build();
    }

    /**
     * 定义Request缓存服务（默认session缓存）
     */
    @Bean
    @ConditionalOnMissingBean
    public RequestCache requestCache() {
        return new HttpSessionRequestCache();
    }

    /**
     * 登录页相关配置
     */
    @ConditionalOnProperty(name = "spring.security.oauth2.authserver.auto-config-login-page", havingValue = "true", matchIfMissing = true)
    @ComponentScan(basePackageClasses = LoginPageController.class)
    @Configuration
    public static class LoginPageConfig {

    }

    /**
     * 添加view映射（登录页面、登出页面、默认登出重定向页面）
     *
     * @param registry view注册中心
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //登出页面URL -> 登出页面View
        registry.addViewController(this.oauth2ServerProps.getLogoutPageUrl()).setViewName(this.oauth2ServerProps.getLogoutPageView());
        //登出后默认跳转页面URL -> 登出后默认跳转页面页面View
        registry.addViewController(this.oauth2ServerProps.getLogoutRedirectDefaultUrl()).setViewName(this.oauth2ServerProps.getLogoutRedirectDefaultView());
    }


    /**
     * 默认定义root/123456用户
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnBean(value = UniLoginUserDetailsPasswordMatcherService.class)
    UserDetailsService users() {
        UserDetails user = User.withUsername("root")
                .password("{bcrypt}$2a$10$iVpQQcCZeA.9iRBEtfjX0.TTMeVJdasYZ.4eh.DFv2oqzWFdoOi0y")
                .authorities("all")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

    /**
     * 定义统一用户查询及验证服务
     *
     * @param userDetailsService
     * @param passwordEncoder
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "spring.security.oauth2.authserver.enable-captcha", havingValue = "false", matchIfMissing = true)
    UniLoginUserDetailsService uniLoginUserDetailsService(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        return new UniLoginUserDetailsPasswordMatcherService(userDetailsService, passwordEncoder);
    }

}
